package com.ideaaedi.commonds.monitor;

import com.alibaba.fastjson2.JSON;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;


/**
 * 耗时统计工具
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2100.10.7.LTS17
 */
public final class WatchTimeUtil {
    
    /** 初始化标识 */
    private static final ThreadLocal<Boolean> initThreadLocal = ThreadLocal.withInitial(() -> false);
    
    /** 存放执行中任务的ThreadLocal */
    private static final ThreadLocal<LinkedList<TaskInfo>> runningTaskThreadLocal = new ThreadLocal<>();
    
    /** 存放已结束任务的ThreadLocal */
    private static final ThreadLocal<LinkedList<TaskInfo>> endTaskThreadLocal = new ThreadLocal<>();
    
    /** 存放最大序号的ThreadLocal */
    private static final ThreadLocal<String> maxTaskNoThreadLocal = new ThreadLocal<>();
    
    /**
     * 开始统计
     * <pre>
     * 若计时器尚未初始化，则会静默忽略当前操作
     * </pre>
     *
     * @param taskName 任务名
     */
    public static void start(String taskName) {
        if (!initThreadLocal.get()) {
            return;
        }
        TaskInfo taskInfo = new TaskInfo();
        String no = Manager.nextNo();
        taskInfo.setTaskNo(no);
        taskInfo.setTaskName(taskName);
        taskInfo.setStartTime(System.nanoTime());
        runningTaskThreadLocal.get().addFirst(taskInfo);
        maxTaskNoThreadLocal.set(no);
    }
    
    /**
     * 结束统计
     * <pre>
     * 若计时器尚未初始化，则会静默忽略当前操作
     * </pre>
     */
    public static void stop() {
        if (!initThreadLocal.get()) {
            return;
        }
        LinkedList<TaskInfo> runningTaskDeque = runningTaskThreadLocal.get();
        if (runningTaskDeque.size() == 0) {
            return;
        }
        TaskInfo taskInfo = runningTaskDeque.pollFirst();
        taskInfo.setEndTime(System.nanoTime());
        endTaskThreadLocal.get().addLast(taskInfo);
    }
    
    /**
     * 执行
     *
     * @param taskName 任务名
     * @param function 业务逻辑块
     * @param param 参数
     *
     * @return 逻辑执行结果
     */
    public static <P, R> R watch(@Nonnull String taskName, Function<P, R> function, P param) {
        try {
            start(taskName);
            return function.apply(param);
        } finally {
            stop();
        }
    }
    
    /**
     * 执行
     *
     * @param taskName 任务名
     * @param function 业务逻辑块
     *
     * @return 执行结果
     */
    public static <R> R watch(@Nonnull String taskName, InnerNoArgFunction<R> function) {
        try {
            start(taskName);
            return function.apply();
        } finally {
            stop();
        }
    }
    
    /**
     * 执行
     *
     * @param taskName 任务名
     * @param consumer 业务逻辑块
     * @param param 参数
     */
    public static <P> void voidWatch(@Nonnull String taskName, Consumer<P> consumer, P param) {
        try {
            start(taskName);
            consumer.accept(param);
        } finally {
            stop();
        }
    }
    
    /**
     * 执行
     *
     * @param taskName 任务名
     * @param consumer 业务逻辑块
     */
    public static void voidWatch(@Nonnull String taskName, InnerNoArgConsumer consumer) {
        try {
            start(taskName);
            consumer.accept();
        } finally {
            stop();
        }
    }
    
    /**
     * 耗时信息
     */
    public static String prettyResult() {
        if (!initThreadLocal.get()) {
            throw new IllegalStateException("Not initialized yet. You could do it by 'WatchTimeUtil.Manager.init()', "
                    + "and don't forget to clear him last by 'WatchTimeUtil.Manager.clear()'");
        }
        if (!Manager.currIsEnd()) {
            throw new IllegalStateException("There are also running tasks. " + Manager.getIngTaskList());
        }
        List<TaskInfo> endTaskList = Manager.getEndTaskList();
        return Manager.prettyResult(endTaskList);
    }
    
    /**
     * 耗时信息
     */
    public static String result() {
        if (!initThreadLocal.get()) {
            throw new IllegalStateException("Not initialized yet. You could do it by 'WatchTimeUtil.Manager.init()', "
                    + "and don't forget to clear him last by 'WatchTimeUtil.Manager.clear()'");
        }
        if (!Manager.currIsEnd()) {
            throw new IllegalStateException("There are also running tasks. " + Manager.getIngTaskList());
        }
        List<TaskInfo> endTaskList = Manager.getEndTaskList();
        return Manager.result(endTaskList);
    }
    
    /**
     * 管理器
     */
    public static class Manager {
        
        /**
         * 初始化
         */
        public static void init() {
            if (initThreadLocal.get()) {
                throw new IllegalStateException("It has already been initialized, please do not initialize it repeatedly.");
            }
            initThreadLocal.set(true);
            runningTaskThreadLocal.set(new LinkedList<>());
            endTaskThreadLocal.set(new LinkedList<>());
        }
        
        /**
         * 初始化
         */
        public static void initSilence() {
            if (initThreadLocal.get()) {
                return;
            }
            initThreadLocal.set(true);
            runningTaskThreadLocal.set(new LinkedList<>());
            endTaskThreadLocal.set(new LinkedList<>());
        }
        
        /**
         * 所有耗时统计任务当前是否都已结束
         */
        public static boolean currIsEnd() {
            LinkedList<TaskInfo> list = runningTaskThreadLocal.get();
            return list == null || list.size() == 0;
        }
        
        /**
         * 获取已完成的任务集合
         */
        public static List<TaskInfo> getEndTaskList() {
            return new ArrayList<>(endTaskThreadLocal.get());
        }
        
        /**
         * 获取进行中的任务集合
         */
        public static List<TaskInfo> getIngTaskList() {
            return new ArrayList<>(runningTaskThreadLocal.get());
        }
        
        /**
         * 清空
         */
        public static void clear() {
            runningTaskThreadLocal.remove();
            endTaskThreadLocal.remove();
            maxTaskNoThreadLocal.remove();
            initThreadLocal.remove();
        }
        
        /**
         * 获取下一个执行序号
         */
        public static String nextNo() {
            TaskInfo taskInfo = runningTaskThreadLocal.get().peekFirst();
            String maxNo = maxTaskNoThreadLocal.get();
            String nextNo;
            if (taskInfo == null) {
                if (maxNo == null) {
                    nextNo = "1";
                } else {
                    nextNo = String.valueOf(Long.parseLong(maxNo.split("\\.")[0]) + 1);
                }
            } else {
                Objects.requireNonNull(maxNo, "maxNo should not be null.");
                String parentNo = taskInfo.getTaskNo();
                String[] parentNoArr = parentNo.split("\\.");
                String[] maxNoArr = maxNo.split("\\.");
                long subNo;
                if (maxNoArr.length > parentNoArr.length) {
                    subNo = Long.parseLong(maxNoArr[parentNoArr.length]) + 1;
                } else {
                    subNo = 1;
                }
                nextNo = parentNo + "." + subNo;
            }
            return nextNo;
        }
        
        /**
         * 按照任务序号排序
         */
        public static void sortByTaskNo(List<TaskInfo> taskInfoList) {
            if (taskInfoList == null) {
                return;
            }
            taskInfoList.sort(
                    (x, y) -> {
                        String xNo = x.getTaskNo();
                        String yNo = y.getTaskNo();
                        String[] xNoArr = xNo.split("\\.");
                        String[] yNoArr = yNo.split("\\.");
                        int minLength = Math.min(xNoArr.length, yNoArr.length);
                        for (int i = 0; i < minLength; i++) {
                            long xVal = Long.parseLong(xNoArr[i]);
                            long yVal = Long.parseLong(yNoArr[i]);
                            if (xVal < yVal) {
                                return -1;
                            }
                            if (xVal > yVal) {
                                return 1;
                            }
                        }
                        return Integer.compare(xNoArr.length, yNoArr.length);
                    }
            );
        }
        
        /**
         * 耗时信息
         */
        public static String prettyResult(@Nonnull List<TaskInfo> endTaskList) {
            StringBuilder sb = new StringBuilder();
            int size = endTaskList.size();
            if (size == 0) {
                sb.append("no tasks time info.");
                return sb.toString();
            }
            if (size == 1) {
                WatchTimeUtil.TaskInfo taskInfo = endTaskList.get(0);
                long consumeMilli = (taskInfo.getEndTime() - taskInfo.getStartTime()) / 1000000;
                return consumeMilli + "ms @ " + taskInfo.getTaskName();
            }
            WatchTimeUtil.Manager.sortByTaskNo(endTaskList);
            WatchTimeUtil.TaskInfo preTask = null;
            for (WatchTimeUtil.TaskInfo taskInfo : endTaskList) {
                String taskNo = taskInfo.getTaskNo();
                long startTime = taskInfo.getStartTime();
                long endTime = taskInfo.getEndTime();
                String[] noArr = taskNo.split("\\.");
                sb.append("\n");
                sb.append(StringUtils.rightPad("|--".repeat(Math.max(0, noArr.length - 1)) + taskNo, 34, "."));
                long consumeMilli = (endTime - startTime) / 1000000;
                // 计算和前一个任务的间隔
                Long intervalMilli = null;
                if (preTask != null) {
                    String preTaskNo = preTask.getTaskNo();
                    long preStartTime = preTask.getStartTime();
                    long preEndTime = preTask.getEndTime();
                    if (preTaskNo.length() > taskNo.length()) {
                        // 前一任务是当前任务的同级前一任务的子任务
                        intervalMilli = (startTime - preEndTime) / 1000000;
                    } else if (taskNo.startsWith(preTaskNo)) {
                        // 前一任务是当前任务的父任务
                        intervalMilli = (startTime - preStartTime) / 1000000;
                    } else {
                        // 同级任务
                        intervalMilli = (startTime - preEndTime) / 1000000;
                    }
                }
                sb.append(StringUtils.leftPad(consumeMilli + "", 6, "."))
                        .append("ms");
                // 两个任务之间的间隔时间
                if (intervalMilli != null) {
                    sb.append("(").append(StringUtils.leftPad(intervalMilli + "ms) @ ", 11, " "));
                } else {
                    sb.append(StringUtils.leftPad(" @ ", 12, " "));
                }
                sb.append(taskInfo.getTaskName());
                preTask = taskInfo;
            }
            return sb.toString();
        }
        
        /**
         * 耗时信息
         */
        public static String result(@Nonnull List<TaskInfo> endTaskList) {
            int size = endTaskList.size();
            if (size == 0) {
                return "no tasks time info.";
            }
            if (size == 1) {
                TaskInfo taskInfo = endTaskList.get(0);
                long consumeMilli = (taskInfo.getEndTime() - taskInfo.getStartTime()) / 1000000;
                return consumeMilli + "ms @ " + taskInfo.getTaskName();
            }
            Manager.sortByTaskNo(endTaskList);
            List<String> list = new ArrayList<>();
            WatchTimeUtil.TaskInfo preTask = null;
            for (TaskInfo taskInfo : endTaskList) {
                StringBuilder sb = new StringBuilder();
                String taskNo = taskInfo.getTaskNo();
                long startTime = taskInfo.getStartTime();
                long endTime = taskInfo.getEndTime();
                sb.append("taskNo: ");
                sb.append(taskNo);
                sb.append(", ");
                long consumeMilli = (taskInfo.getEndTime() - taskInfo.getStartTime()) / 1000000;
                // 计算和前一个任务的间隔
                Long intervalMilli = null;
                if (preTask != null) {
                    String preTaskNo = preTask.getTaskNo();
                    long preStartTime = preTask.getStartTime();
                    long preEndTime = preTask.getEndTime();
                    if (preTaskNo.length() > taskNo.length()) {
                        // 前一任务是当前任务的同级前一任务的子任务
                        intervalMilli = (startTime - preEndTime) / 1000000;
                    } else if (taskNo.startsWith(preTaskNo)) {
                        // 前一任务是当前任务的父任务
                        intervalMilli = (startTime - preStartTime) / 1000000;
                    } else {
                        // 同级任务
                        intervalMilli = (startTime - preEndTime) / 1000000;
                    }
                }
                sb.append("consumeTime: ");
                sb.append(consumeMilli).append("ms, ");
                if (intervalMilli != null) {
                    sb.append("intervalTime: ").append(intervalMilli).append("ms, ");
                }
                sb.append("taskName: ")
                        .append(taskInfo.getTaskName());
                list.add(sb.toString());
                preTask = taskInfo;
            }
            return JSON.toJSONString(list);
        }
    }
    
    /**
     * 任务信息
     */
    public static class TaskInfo {
        
        /** 执行序号（格式形如：xxx.xxx.xxx） */
        private String taskNo;
        
        /** 任务名 */
        private String taskName;
        
        /** 开始时间（纳秒） */
        private long startTime;
        
        /** 结束时间（纳秒） */
        private long endTime;
    
        public String getTaskNo() {
            return taskNo;
        }
    
        public void setTaskNo(String taskNo) {
            this.taskNo = taskNo;
        }
    
        public String getTaskName() {
            return taskName;
        }
    
        public void setTaskName(String taskName) {
            this.taskName = taskName;
        }
    
        public long getStartTime() {
            return startTime;
        }
    
        public void setStartTime(long startTime) {
            this.startTime = startTime;
        }
    
        public long getEndTime() {
            return endTime;
        }
    
        public void setEndTime(long endTime) {
            this.endTime = endTime;
        }
    
        @Override
        public String toString() {
            return "TaskInfo{" +
                    "taskNo='" + taskNo + '\'' +
                    ", taskName='" + taskName + '\'' +
                    ", startTime=" + startTime +
                    ", endTime=" + endTime +
                    '}';
        }
    }
    
    /**
     * (non-javadoc)
     */
    @FunctionalInterface
    public interface InnerNoArgFunction<R> {
        R apply();
    }
    
    /**
     * (non-javadoc)
     */
    @FunctionalInterface
    public interface InnerNoArgConsumer {
        void accept();
    }
    
}
